package com.csjung.games.gamenori.client;

import com.google.gwt.activity.shared.ActivityManager;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceChangeEvent;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.place.shared.PlaceHistoryHandler;
import com.google.gwt.storage.client.Storage;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.inject.Inject;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanCodex;
import com.google.web.bindery.event.shared.EventBus;
import com.google.web.bindery.event.shared.UmbrellaException;
import com.google.web.bindery.requestfactory.shared.Receiver;

import com.csjung.games.gamenori.client.activity.AppActivityMapper;
import com.csjung.games.gamenori.client.activity.AppPlaceHistoryMapper;
import com.csjung.games.gamenori.client.event.OnMessageEvent;
import com.csjung.games.gamenori.client.event.UserListRefreshEvent;
import com.csjung.games.gamenori.client.place.WelcomePlace;
import com.csjung.games.gamenori.client.ui.ChatDialogBox;
import com.csjung.games.gamenori.shared.ChannelMsgs;
import com.csjung.games.gamenori.shared.ChannelProxy;
import com.csjung.games.gamenori.shared.GamenoriFactory;
import com.csjung.games.gamenori.shared.GamenoriRequestFactory;
import com.csjung.games.gamenori.shared.UserServiceRequest;

import java.util.logging.Level;
import java.util.logging.Logger;

public class DesktopApp extends MainApp {

  private static final String HISTORY_SAVE_KEY = "SAVEPLACE";

  private static final Logger log = Logger.getLogger(MainApp.class.getName());

  private GamenoriFactory factory = GWT.create(GamenoriFactory.class);
  
  /**
   * Where components of the app converse by posting and monitoring events.
   */
  private final EventBus eventBus;
  
  private final Storage storage;

  private final AppPlaceHistoryMapper historyMapper;

  private final PlaceHistoryHandler historyHandler;

  private final PlaceController placeController;

  private final AppActivityMapper appActivityMapper;
  
  private final GamenoriShell gamenoriShell;
  
  private final GamenoriRequestFactory requestFactory;
  
  @Inject
  public DesktopApp(EventBus eventBus, 
      AppActivityMapper appActivityMapper,
      Storage storage, 
      AppPlaceHistoryMapper historyMapper, 
      PlaceHistoryHandler historyHandler,
      PlaceController placeController,
      GamenoriShell gamenoriShell,
      GamenoriRequestFactory requestFactory) {
    this.eventBus = eventBus;
    this.storage = storage;
    this.historyMapper = historyMapper;
    this.historyHandler = historyHandler;
    this.placeController = placeController;
    this.appActivityMapper = appActivityMapper;
    this.gamenoriShell = gamenoriShell;
    this.requestFactory = requestFactory;
  }

  /**
   * Given a parent view to show itself in, start this App.
   * 
   * @param parentView where to show the app's widget
   */
  @Override
  public void run(HasWidgets.ForIsWidget parentView) {

    /* Hide the loading message */
    Element loading = Document.get().getElementById("loading");
    loading.getParentElement().removeChild(loading);
    ActivityManager activityManager = new ActivityManager(appActivityMapper, eventBus);
    activityManager.setDisplay(gamenoriShell);
    parentView.add(gamenoriShell);

    GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
      @Override
      public void onUncaughtException(Throwable e) {
        while (e instanceof UmbrellaException) {
          e = ((UmbrellaException) e).getCauses().iterator().next();
        }

        String message = e.getMessage();
        if (message == null) {
          message = e.toString();
        }
        log.log(Level.SEVERE, "Uncaught exception", e);
        Window.alert("An unexpected error occurred: " + message);
      }
    });
    
    Window.addWindowClosingHandler(new Window.ClosingHandler() {
      public void onWindowClosing(Window.ClosingEvent closingEvent) {
        UserServiceRequest request = requestFactory.userServiceRequest();
        request.notifyLogout("");
        request.fire(new Receiver<Void>() {
          @Override
          public void onSuccess(Void ignore) {
          }
        });
      }
    });

    eventBus.addHandler(OnMessageEvent.TYPE, new OnMessageEvent.Handler() {
      public void onMessage(OnMessageEvent event) {
       parseMessage(event.getMessage());
      }
    });

    initBrowserHistory(historyMapper, historyHandler, new WelcomePlace());
  }
  
  /**
   * Initialize browser history / bookmarking. If LocalStorage is available, use it to make the
   * user's default location in the app the last one seen.
   */
  private void initBrowserHistory(final AppPlaceHistoryMapper historyMapper,
      PlaceHistoryHandler historyHandler, WelcomePlace defaultPlace) {

    Place savedPlace = null;
    if (storage != null) {
      try {
        // wrap in try-catch in case stored value is invalid
        savedPlace = historyMapper.getPlace(storage.getItem(HISTORY_SAVE_KEY));
      } catch (Throwable t) {
        // ignore error and use the default-default
      }
    }
    if (savedPlace == null) {
      savedPlace = defaultPlace;
    }
    historyHandler.register(placeController, eventBus, savedPlace);

    /*
     * Go to the place represented in the URL. This is what makes bookmarks work.
     */
    historyHandler.handleCurrentHistory();

    /*
     * Monitor the eventbus for place changes and note them in LocalStorage for the next launch.
     */
    if (storage != null) {
      eventBus.addHandler(PlaceChangeEvent.TYPE, new PlaceChangeEvent.Handler() {
        public void onPlaceChange(PlaceChangeEvent event) {
          storage.setItem(HISTORY_SAVE_KEY, historyMapper.getToken(event.getNewPlace()));
        }
      });
    }
  }

  private void parseMessage(String message) {
    System.out.println("LoginWidget->message: " + message);
    AutoBean<ChannelProxy> bean = AutoBeanCodex.decode(factory, ChannelProxy.class, message);
    ChannelProxy channel = bean.as();
    if (ChannelMsgs.NEWUSER.equals(channel.getChannelType())) {
      eventBus.fireEventFromSource(new UserListRefreshEvent(), this);
    } else if (ChannelMsgs.LOGOUT.equals(channel.getChannelType())) {                  
      eventBus.fireEventFromSource(new UserListRefreshEvent(), this);
    } else if (ChannelMsgs.REQUEST_CHAT.equals(channel.getChannelType())) {
      ChatDialogBox chatBox = new ChatDialogBox(requestFactory);
      chatBox.setAnimationEnabled(true);
      chatBox.center();
      chatBox.contactChannel(channel);
    }
  }

}
